package main

import (
	"archive/tar"
	"bytes"
	"debug/elf"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"mime"
	"os"
	"path/filepath"
	"regexp"
	"time"
)

// ELFMagicNumber представляет собой сигнатуру ELF-файла
var ELFMagicNumber = []byte{0x7F, 'E', 'L', 'F'}

type FileInfo struct {
	FileName         string    `json:"fileName"`
	FilePath         string    `json:"filePath"`
	FileSize         uint      `json:"fileSize"`
	FilePermision    string    `json:"filePermision"`
	FileModified     time.Time `json:"fileModified"`
	FileMimeType     string    `json:"fileMimeType"`
	FileUser         string    `json:"fileUser"`
	FileGroup        string    `json:"fileGroup"`
	FileDependencies []string  `json:"dependencies"`
}

const pathStart string = "/run/media/lobasov/etc/bpo/spo/АФЕК.11057-04-M"

func main() {
	listFiles := []FileInfo{}
	listFiles = dirScan(pathStart)
	json.Marshal(listFiles)
	// out, _ := json.MarshalIndent(listFiles, "", " ")
	// fmt.Println(string(out))
}

func dirScan(path string) []FileInfo {
	files, err := os.ReadDir(path)
	if err != nil {
		log.Printf("Ошибка при сканировании директории: %v", err)
	}

	var result []FileInfo

	for _, file := range files {
		if file.IsDir() {
			result = append(result, dirScan(path+"/"+file.Name())...)
		} else {
			mimeType := mime.TypeByExtension(filepath.Ext(file.Name()))
			// fmt.Println(mimeType)
			f, err := file.Info()
			if err != nil {
				log.Printf("Ошибка при чтении файла: %v", err)
			}

			addFile := FileInfo{
				FileName: file.Name(),
				FilePath: func() string {
					if path == pathStart {
						return ""
					} else {
						return path
					}
				}(),
				FileSize:      uint(f.Size()),
				FilePermision: f.Mode().String(),
				FileModified:  f.ModTime(),
				FileMimeType:  mimeType,
			}

			if mimeType == "application/x-tar" {
				infoArchive := getInfoArchive(path + "/" + file.Name())
				result = append(result, infoArchive[:]...)
			}

			result = append(result, addFile)
		}
	}

	return result
}
func getInfoArchive(filePath string) []FileInfo {
	// fmt.Println("getInfoArchive", filePath)
	file, err := os.Open(filePath)
	if err != nil {
		log.Printf("Ошибка при открытии архива: %v", err)
	}
	defer file.Close()

	archive := tar.NewReader(file)
	// fmt.Println(archive)

	var result []FileInfo

	for {
		header, err := archive.Next()
		// fmt.Println("archive.Next ->", archive)
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Printf("Ошибка при чтении содержимого архива: %v", err)
		}

		// TODO: доделать определение MIME типа файла
		// mimeType := mime.TypeByExtension(filepath.Ext(file.Name()))
		// fmt.Println("mimeType ->", mimeType)

		re := regexp.MustCompilePOSIX(`^(.*/)?(|)`)

		fileInfo := FileInfo{
			FileName:      header.FileInfo().Name(),
			FilePath:      re.FindStringSubmatch(header.Name)[0],
			FileSize:      uint(header.Size),
			FilePermision: header.FileInfo().Mode().String(),
			FileUser:      header.Uname,
			FileGroup:     header.Gname,
			FileModified:  header.ModTime,
		}

		if header.FileInfo().Mode().IsRegular() {
			// Создаем буфер для хранения содержимого файла
			var buf bytes.Buffer
			_, err := io.Copy(&buf, archive)
			if err != nil {
				log.Printf("Ошибка при копировании содержимого файла: %v", err)
				continue
			}

			// Используем bytes.Reader, который реализует io.ReadSeeker
			reader := bytes.NewReader(buf.Bytes())

			if isELF(reader) {
				// fmt.Println("FILE ->", header.Name)
				dependencies, err := getELFDependenciesFromArchive(reader)
				if err != nil {
					log.Printf("Ошибка при получении зависимостей: %v", err)
				} else {
					fileInfo.FileDependencies = dependencies
				}
			}
		}

		result = append(result, fileInfo)
	}

	return result
}

// Функция считывает первые 4 байта из файла и проверяет, соответствуют ли они магическому числу ELF (0x7F, 'E', 'L', 'F').
// Если магическое число найдено, возвращается значение true, что указывает на то, что считыватель содержит файл ELF.
// В противном случае возвращается значение false.
func isELF(file io.ReadSeeker) bool {
	var buf [4]byte
	_, err := file.Read(buf[:])
	if err != nil {
		log.Printf("isELF: Ошибка при чтении файла: %v", err)
		return false
	}

	// Сброс позиции чтения в начало
	_, err = file.Seek(0, io.SeekStart)
	if err != nil {
		log.Printf("isELF: Ошибка при сбросе позиции чтения: %v", err)
		return false
	}

	return bytes.Equal(buf[:], ELFMagicNumber)
}

func getELFDependenciesFromArchive(file io.Reader) ([]string, error) {
	buf := new(bytes.Buffer)
	_, err := io.Copy(buf, file)
	if err != nil {
		return nil, fmt.Errorf("ошибка при копировании в буфер ELF-файла: %v", err)
	}

	f, err := elf.NewFile(bytes.NewReader(buf.Bytes()))
	if err != nil {
		return nil, fmt.Errorf("ошибка при создании ELF-файла: %v", err)
	}
	defer f.Close()

	var dependencies []string

	dynstr := f.Section(".dynstr")
	if dynstr == nil {
		return nil, fmt.Errorf("секция .dynstr не найдена")
	}

	strdata, err := dynstr.Data()
	if err != nil {
		return nil, fmt.Errorf("ошибка при чтении секции .dynstr: %v", err)
	}

	section := f.Section(".dynamic")
	if section != nil {
		data, err := section.Data()
		if err != nil {
			return nil, fmt.Errorf("ошибка при чтении секции .dynamic: %v", err)
		}

		var classSize int
		if f.Class == elf.ELFCLASS32 {
			classSize = 4
		} else {
			classSize = 8
		}

		for i := 0; i < len(data); i += classSize * 2 {
			var tag elf.DynTag
			if f.Class == elf.ELFCLASS32 {
				tag = elf.DynTag(f.ByteOrder.Uint32(data[i:]))
			} else {
				tag = elf.DynTag(f.ByteOrder.Uint64(data[i:]))
			}

			if tag == elf.DT_NEEDED {
				var val uint64
				if f.Class == elf.ELFCLASS32 {
					val = uint64(f.ByteOrder.Uint32(data[i+classSize:]))
				} else {
					val = f.ByteOrder.Uint64(data[i+classSize:])
				}

				if int(val) < len(strdata) {
					end := int(val)
					for end < len(strdata) && strdata[end] != 0 {
						end++
					}
					name := string(strdata[val:end])
					dependencies = append(dependencies, name)
				}
			}
		}
	}

	return dependencies, nil
}
